﻿namespace Blaze.Security
{
    using System;
    using System.Text;

    /// <summary>
    /// 用于内存加密<see cref="string"/>型的变量。
    /// </summary>
    public sealed class SecureString
    {
        /// <summary>
        /// 当检测到内存作弊时调用此方法（只会触发一次，之后会被设为null）
        /// </summary>
        public static Action onCheatingDetected;

        private SecureString(string value)
        {
            currentCryptoKey = cryptoKey;
            hiddenValue = value;
            fakeValue = null;
            inited = true;
        }

        /// <summary>
        /// 使用默认的密钥加密/解密指定的变量。
        /// </summary>
        /// <param name="value">密文或明文</param>
        /// <returns>明文或密文</returns>
        public static string EncryptDecrypt(string value)
        {
            return EncryptDecrypt(value, "");
        }

        /// <summary>
        /// 使用指定的密钥加密/解密指定的变量。
        /// </summary>
        /// <param name="value">密文或明文</param>
        /// <param name="key">密钥</param>
        /// <returns>明文或密文</returns>
        public static string EncryptDecrypt(string value, string key)
        {
            if (string.IsNullOrEmpty(value))
            {
                return string.Empty;
            }

            if (string.IsNullOrEmpty(key))
            {
                key = cryptoKey;
            }

            var result = new StringBuilder();
            var keyLength = key.Length;
            var valueLength = value.Length;

            for (var i = 0; i < valueLength; i++)
            {
                result.Append((char) (value[i] ^ key[i%keyLength]));
            }

            return result.ToString();
        }

        public static bool operator ==(SecureString a, SecureString b)
        {
            if (ReferenceEquals(a, b))
            {
                return true;
            }

            if (((object) a == null) || ((object) b == null))
            {
                return false;
            }

            string strA = a.hiddenValue;
            string strB = b.hiddenValue;

            return string.Equals(strA, strB);
        }

        public static implicit operator SecureString(string value)
        {
            if (value == null)
            {
                return null;
            }

            var secure = new SecureString(EncryptDecrypt(value));
            if (onCheatingDetected != null)
            {
                secure.fakeValue = value;
            }
            return secure;
        }

        public static implicit operator string(SecureString value)
        {
            if (value == null)
            {
                return null;
            }
            return value.InternalDecrypt();
        }

        public static bool operator !=(SecureString a, SecureString b)
        {
            return !(a == b);
        }

        /// <summary>
        /// 设置新的加密密钥。
        /// </summary>
        /// <param name="newKey">新的密钥</param>
        public static void SetNewCryptoKey(string newKey)
        {
            cryptoKey = newKey;
        }

        public override bool Equals(object obj)
        {
            var strA = obj as SecureString;
            string strB = null;
            if (strA != null) strB = strA.hiddenValue;

            return string.Equals(hiddenValue, strB);
        }

        public bool Equals(SecureString value)
        {
            string strA = null;
            if (value != null) strA = value.hiddenValue;

            return string.Equals(hiddenValue, strA);
        }

        public bool Equals(SecureString value, StringComparison comparisonType)
        {
            string strA = null;
            if (value != null) strA = value.InternalDecrypt();

            return string.Equals(InternalDecrypt(), strA, comparisonType);
        }

        /// <summary>
        /// 获取加密后的值。
        /// </summary>
        public string GetEncrypted()
        {
            return hiddenValue;
        }

        public override int GetHashCode()
        {
            return InternalDecrypt().GetHashCode();
        }

        /// <summary>
        /// 将变量设置为加密后的值。
        /// </summary>
        /// <param name="encrypted">加密后的值</param>
        public void SetEncrypted(string encrypted)
        {
            hiddenValue = encrypted;
            if (onCheatingDetected != null)
            {
                fakeValue = InternalDecrypt();
            }
        }

        public override string ToString()
        {
            return InternalDecrypt();
        }

        private string InternalDecrypt()
        {
            if (!inited)
            {
                currentCryptoKey = cryptoKey;
                hiddenValue = EncryptDecrypt(string.Empty);
                fakeValue = string.Empty;
                inited = true;
            }

            var key = cryptoKey;

            if (currentCryptoKey != cryptoKey)
            {
                key = currentCryptoKey;
            }

            string result = EncryptDecrypt(hiddenValue, key);

            if (onCheatingDetected != null && fakeValue != null && result != fakeValue)
            {
                onCheatingDetected();
                onCheatingDetected = null;
            }

            return result;
        }

        private static string cryptoKey = "qingyue";

        private string currentCryptoKey;
        private string hiddenValue;
        private string fakeValue;
        private bool inited;
    }
}